package com.lemon.common.util;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.stereotype.Component;

/**
 * @author lemon
 * @description 非 Spring Bean 可以访问 Spring 管理的 Bean
 * @date 2020-07-05 08:11
 */
@Slf4j
@Component
public class BeanDefinitionRegistryUtil implements BeanDefinitionRegistryPostProcessor {

    /**
     * @description Spring应用上下文环境
     * @author lemon
     * @date 2019/2/22 12:04
     */
    private static BeanDefinitionRegistry registry;

    /**
     * @description Spring应用上下文环境
     * @author lemon
     * @date 2019/2/22 12:04
     */
    private static ConfigurableListableBeanFactory beanFactory;

    /**
     * Modify the application context's internal bean definition registry after its
     * standard initialization. All regular bean definitions will have been loaded,
     * but no beans will have been instantiated yet. This allows for adding further
     * bean definitions before the next post-processing phase kicks in.
     *
     * @param registry the bean definition registry used by the application context
     * @throws BeansException in case of errors
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        BeanDefinitionRegistryUtil.registry = registry;

        log.debug("BeanDefinitionRegistry is {}", registry);
    }

    /**
     * Modify the application context's internal bean factory after its standard
     * initialization. All bean definitions will have been loaded, but no beans
     * will have been instantiated yet. This allows for overriding or adding
     * properties even to eager-initializing beans.
     *
     * @param beanFactory the bean factory used by the application context
     * @throws BeansException in case of errors
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinitionRegistryUtil.beanFactory = beanFactory;

        log.debug("ConfigurableListableBeanFactory is {}", beanFactory);
    }

    /**
     * @param
     * @return
     * @description 通过类获取
     * @author lemon
     * @date 2019/2/22 12:06
     */
    public static <T> T getBean(Class<T> clazz) throws BeansException {
        return BeanDefinitionRegistryUtil.beanFactory.getBean(clazz);
    }

    /**
     * @param
     * @return
     * @description 通过名字获取
     * @author lemon
     * @date 2019/2/22 12:05
     */
    public static <T> T getBean(String name) throws BeansException {
        return (T) BeanDefinitionRegistryUtil.beanFactory.getBean(name);
    }

    /**
     * @param
     * @return
     * @description 通过名字获取
     * @author lemon
     * @date 2019/2/22 12:05
     */
    public static <T> T getBean(String name, Class<T> clazz) throws BeansException {
        return BeanDefinitionRegistryUtil.beanFactory.getBean(name, clazz);
    }

    /**
     * @param
     * @return
     * @description 通过名字获取
     * @author lemon
     * @date 2019/2/22 12:05
     */
    public static BeanDefinition getBeanDefinition(String name) throws BeansException {
        return BeanDefinitionRegistryUtil.beanFactory.getBeanDefinition(name);
    }

    /**
     * @param name
     * @return boolean
     * @description
     * @author lemon
     * @date 2020-07-10 09:50
     */
    public static boolean containsBeanDefinition(String name) throws BeansException {
        return BeanDefinitionRegistryUtil.beanFactory.containsBeanDefinition(name);
    }

    /**
     * @param beanName
     * @param clazz
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-10 12:11
     */
    public static <T> void registerBeanDefinition(String beanName, Class<T> clazz) {
        BeanDefinitionRegistryUtil.registerBeanDefinition(beanName, clazz, true, true);
    }

    /**
     * @param beanName
     * @param clazz
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-10 12:17
     */
    public static <T> void registerBeanDefinitionNoDelete(String beanName, Class<T> clazz) {
        BeanDefinitionRegistryUtil.registerBeanDefinition(beanName, clazz, true, false);
    }

    /**
     * @param beanName
     * @param clazz
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-10 12:17
     */
    public static <T> void registerBeanDefinitionNoPrimary(String beanName, Class<T> clazz) {
        BeanDefinitionRegistryUtil.registerBeanDefinition(beanName, clazz, false, true);
    }

    /**
     * @param beanName
     * @param clazz
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-10 12:17
     */
    public static <T> void registerBeanDefinitionNoPrimaryDelete(String beanName, Class<T> clazz) {
        BeanDefinitionRegistryUtil.registerBeanDefinition(beanName, clazz, false, false);
    }

    /**
     * @param beanName
     * @param clazz
     * @param primary
     * @param deleteWhenExist
     * @return void
     * @description 注册 Bean
     * @author lemon
     * @date 2020-07-10 12:09
     */
    public static <T> void registerBeanDefinition(String beanName, Class<T> clazz, boolean primary, boolean deleteWhenExist) {
        boolean exist = BeanDefinitionRegistryUtil.registry.containsBeanDefinition(beanName);

        if (exist && !deleteWhenExist) {
            BeanDefinition beanDefinition = BeanDefinitionRegistryUtil.registry.getBeanDefinition(beanName);
            log.debug("{} existing BeanDefinition [{}", beanName, beanDefinition);
            return;
        }

        if (exist && deleteWhenExist) {
            BeanDefinition beanDefinition = BeanDefinitionRegistryUtil.registry.getBeanDefinition(beanName);
            BeanDefinitionRegistryUtil.registry.removeBeanDefinition(beanName);
            log.debug("Delete {}'s existing BeanDefinition [{}", beanName, beanDefinition);
        }

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        beanDefinition.setPrimary(primary);

        BeanDefinitionRegistryUtil.registry.registerBeanDefinition(beanName, beanDefinition);
        log.debug("Register [{}] BeanDefinition [{}] successfully", beanName, BeanDefinitionRegistryUtil.registry.getBeanDefinition(beanName));
    }

    /**
     * @param beanName
     * @param singletonObject
     * @return void
     * @description 注册单例对象
     * @author lemon
     * @date 2020-07-09 14:53
     */
    public static void registerSingleton(String beanName, Object singletonObject) {
        BeanDefinitionRegistryUtil.beanFactory.registerSingleton(beanName, singletonObject);
        log.debug("Register Bean [{}] [{}] successfully", beanName, singletonObject);
    }
}